/**
 * 
 */
package jvs.peer.xmpp;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import jvs.peer.io.ChannelManager;
import jvs.peer.io.Lifecycle;
import jvs.peer.io.MessageConstant;
import jvs.peer.io.PeerStatus.Status;
import jvs.peer.sec.User;
import jvs.peer.util.Log;
import jvs.sh.IShell;

import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.StringUtils;

/**
 * @author qiangli
 * 
 */
public class XmppConnectHandler implements PacketListener, Lifecycle {
	private static final int MAX_CONNECTION = 100;

	private long maxIdle = 60 * 1000;

	private long checkInterval = 5 * 1000;

	private boolean running = false;

	private class Cleaner extends Thread {
		@Override
		public void run() {
			running = true;
			while (running) {
				try {
					long now = System.currentTimeMillis();
					for (Iterator<Map.Entry<String, XmppShellWrapper>> it = shells
							.entrySet().iterator(); it.hasNext();) {
						Map.Entry<String, XmppShellWrapper> m = it.next();
						XmppShellWrapper w = m.getValue();
						IShell sh = w.getShell();
						long elapsed = now - sh.getLastAccess();
						if (elapsed > maxIdle) {
							try {
								sh.exit();
							} catch (Exception e) {
								e.printStackTrace();
							}
							it.remove();
						}
					}
					Thread.sleep(checkInterval);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	private Map<String, XmppShellWrapper> shells = new HashMap<String, XmppShellWrapper>();
	private XMPPConnection conn;

	private Cleaner cleaner;

	private XmppWebAdapter adapter;

	public XmppConnectHandler(XMPPConnection conn, XmppWebAdapter adapter) {
		this.conn = conn;
		this.adapter = adapter;
	}

	private static long instance = 0;
	private static String prefix = StringUtils.randomString(5);

	private static synchronized String nextId() {
		return "ishell-" + prefix + Long.toString(instance++);
	}

	public void processPacket(Packet packet) {
		try {
			Message msg = (Message) packet;
			String peer = msg.getFrom();
			if (peer == null) {
				return;
			}
			String tid = msg.getThread();
			if (tid == null) {
				tid = nextId();
			}
			Object prop = msg.getProperty(MessageConstant.SHELL);
			if (prop != null) {
				Integer type = (Integer) prop;
				if (type.intValue() == MessageConstant.response) {
					if (Log.DEBUG) {
						Log.log(this, " response ignored: " + peer + " tid: " + tid
							+ " msg: " + msg);
					}
					return;
				}
			}
			//
			synchronized (shells) {
				// max exceeded?
				boolean exceeded = (shells.size() >= getMaxConnection());
				if (exceeded) {
					replyMessage(msg,
							"500 Sorry, we are busy, please try again later.");
					return;
				}
				//
				XmppShellWrapper wrap = shells.get(peer);
				if (wrap == null) {
					//
					String cmd = msg.getBody().toUpperCase();
					if (cmd.equals("BYE") || cmd.equals("QUIT") || cmd.equals("EXIT") || cmd.equals("DISCONNECT")) {
						return; //ignore
					}
					//
					wrap = new XmppShellWrapper(this, peer, tid, conn, adapter);
					shells.put(peer, wrap);
					wrap.connect();
					//
					wrap.processPacket(packet); // process current packet
					//
					if (Log.DEBUG) {
						Log.log(this, "new shell connected: " + wrap + " shells: "
							+ shells.size());
					}
					// check again if max reached
					exceeded = (shells.size() >= getMaxConnection());
					if (exceeded) {
						ChannelManager.getInstance().changeStatus(Status.BUSY);
					}
				}
				if (Log.DEBUG) {
					Log.log(this, "shell: " + wrap + " shells: " + shells.size());
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private int getMaxConnection() {
		return MAX_CONNECTION;
	}

	private void replyMessage(Message in, String message) {
		Message msg = new Message();
		msg.setThread(in.getThread());
		msg.setTo(in.getFrom());
		msg.setType(in.getType());
		msg.setBody(message);
		msg.setFrom(in.getTo());
		msg.setProperty(MessageConstant.SHELL, MessageConstant.response);
		conn.sendPacket(msg);
	}

	public List<User> listUser() throws Exception {
		ArrayList<User> users = new ArrayList<User>();
		for (XmppShellWrapper sh : shells.values()) {
			users.add(sh.getUser());
		}
		return users;
	}

	public String getName() {
		return conn.getServiceName();
	}

	public void terminate(String peer) {
		XmppShellWrapper sh = shells.remove(peer);
		if (Log.DEBUG) {
			Log.log(this, "removed: " + sh);
		}
	}

	public void init(Properties props) {
	}

	public void release() {
	}

	public void shutdown() throws Exception {
		running = false;
		cleaner.interrupt();
		cleaner = null;
	}

	public void startup() throws Exception {
		cleaner = new Cleaner();
		cleaner.setDaemon(true);
		cleaner.start();
	}
}
